﻿using CloudStorageLight.Core.Web;
using Microsoft.Azure.KeyVault;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;

namespace CloudStorageLight.Core
{
    public class AzureBlobContainer : BlobContainer
    {
        //protected const string metaTagArchiveLastModified = "ArchiveLastModified";

        internal const string keyLastWriteTime = "LastWriteTime";
        internal const string keyCreationTime = "CreationTime";

        private CloudBlobContainer fileContainer;
        internal protected string containerName;

        protected CloudStorageAccount StorageAccount { get; set; }
        protected CloudBlobClient BlobClient { get; set; }

        protected string BackupContainerName { get; set; }
        protected CloudBlobContainer BackupContainer { get; set; }

        protected BlobEncryptionPolicy encryptionPolicy;

        protected bool IsEncrypted { get; private set; }

        public AzureBlobContainer(string containerName, string connectionString)
        {
            this.containerName = containerName;
            //this.RootPath = rootPath;
            if (connectionString != null)
            {
                StorageAccount = CloudStorageAccount.Parse(connectionString); //CloudConfigurationManager.GetSetting("blobStorage"));
                BlobClient = StorageAccount.CreateCloudBlobClient();
                fileContainer = BlobClient.GetContainerReference(containerName);
                fileContainer.CreateIfNotExists();
            }
            this.BackupContainerName = containerName;
            this.BackupContainer = fileContainer;

            this.IsEncrypted = !string.IsNullOrEmpty(SystemSettings.Instance.BlobEncryptKey);
            if (IsEncrypted)
            {
                var keyBytes = Encoding.UTF8.GetBytes(SystemSettings.Instance.BlobEncryptKey); //256 bits
                var symmetricKey = new SymmetricKey("BlobEncryptKey", keyBytes);

                // upload test file.
                encryptionPolicy = new BlobEncryptionPolicy(symmetricKey, null);
            }
        }

        /// <summary>
        /// コンテナで利用している容量サイズを返す
        /// </summary>
        /// <returns></returns>
        public override async Task<long> GetContainerSizeAsync()
        {
            var size = await GetContainerSizeAsync(containerName);
            //var tempCont = BlobClient.GetContainerReference(containerName + "-temp");
            //if (await tempCont.ExistsAsync()) size += await GetContainerSizeAsync(containerName + "-temp");
            //var catalogCont = BlobClient.GetContainerReference(containerName + "-catalog");
            //if (await catalogCont.ExistsAsync()) size += await GetContainerSizeAsync(containerName + "-catalog");
            //var trashCont = BlobClient.GetContainerReference(containerName + "-trash");
            //if (await trashCont.ExistsAsync()) size += await GetContainerSizeAsync(containerName + "-trash");
            return size;
        }

        /// <summary>
        /// コンテナのサイズを返す
        /// </summary>
        /// <param name="containerName"></param>
        /// <returns></returns>
        protected async Task<long> GetContainerSizeAsync(string containerName)
        {
            long size = 0;
            BlobContinuationToken token = null;
            var paths = new List<string>();
            var blobContainer = BlobClient.GetContainerReference(containerName);
            while (true)
            {
                var list = await blobContainer.ListBlobsSegmentedAsync(null, true, BlobListingDetails.None, null, token, null, null);
                if (list != null)
                {
                    token = list.ContinuationToken;
                    foreach (var item in list.Results.OfType<CloudBlockBlob>())
                    {
                        size += item.Properties.Length;
                    }
                    if (token == null) break;
                }
                else
                {
                    break;
                }
            }
            return size;
        }

        ///// <summary>
        ///// 指定したプレフィックスですべてのBlobを取得する
        ///// 
        ///// プレフィックスにnullを指定すると全件取得する
        ///// </summary>
        ///// <param name="prefix"></param>
        ///// <returns></returns>
        //public override IEnumerable<BlobItem> ListBlob(string prefix)
        //{
        //    foreach (var item in fileContainer.ListBlobs(prefix, true).OfType<CloudBlockBlob>())
        //    {
        //        var itemPath = BlobUtil.UnescapeDataString(item.Uri.PathAndQuery).Substring(containerName.Length + 1);
        //        yield return new BlobFile(this, RootPath + itemPath);
        //    }
        //}


        /// <summary>
        /// 指定したファイルが存在するか
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public override async Task<bool> ExistsAsync(string path)
        {
            if (path == "/") return true;
            var item = new BlobItem(this, path);
            if (path.EndsWith("/"))
            {
                var list = await fileContainer.ListBlobsSegmentedAsync(item.RelativePath, true, BlobListingDetails.None, 1, null, null, null);
                return list != null && list.Results.Count() > 0;
            }
            else
            {
                var blob = fileContainer.GetBlockBlobReference(item.RelativePath);
                if (!await blob.ExistsAsync()) return false;
                var file = await EnsureFileAsync(path, true);
                if (file.LastModified == null) return false;
                return true;
            }

        }


        /// <summary>
        /// Blobのコピー
        /// </summary>
        /// <param name="targetPath"></param>
        /// <param name="fromUri"></param>
        /// <returns></returns>
        public override async Task CopyAsync(BlobFile srcFile, string targetPath)
        {
            var item = new BlobFile(this, targetPath);

            var tempFile = Path.GetTempFileName();
            try
            {
                await srcFile.DownloadAsync(tempFile);
                await item.UploadAsync(tempFile);
            }
            finally
            {
                if (File.Exists(tempFile)) File.Delete(tempFile);
            }

        }


        public override async Task CopyWithSnapshotAsync(BlobFile src, BlobFile target)
        {
            var srcBlobs = BackupContainer.ListBlobs(src.RelativePath, true, BlobListingDetails.Snapshots | BlobListingDetails.Metadata).ToList();
            var targetBlob = BackupContainer.GetBlockBlobReference(target.RelativePath);
            foreach (var srcBlob in srcBlobs.Cast<CloudBlockBlob>().Where(b => b.Name==src.RelativePath && b.SnapshotTime != null).OrderBy(b=>b.SnapshotTime))
            {
                var blob = await CopyBlobAsync(srcBlob, targetBlob);
                await blob.SnapshotAsync();
            }
        }

        protected async Task<CloudBlockBlob> CopyBlobAsync(CloudBlockBlob srcBlob, CloudBlockBlob targetBlob)
        {
            await targetBlob.StartCopyAsync((CloudBlockBlob)srcBlob);
            foreach (var meta in srcBlob.Metadata)
            {
                targetBlob.Metadata[meta.Key] = meta.Value;
            }
            await targetBlob.SetMetadataAsync();
            return targetBlob;
        }

        /// <summary>
        /// 削除
        /// </summary>
        /// <returns></returns>
        public override async Task<bool> DeleteAsync(BlobItem file)
        {
            file.IsDeleted = true;
            await SetMetadataAsync(file);
            return true;
        }

        /// <summary>
        /// 削除
        /// </summary>
        /// <returns></returns>
        public override async Task<bool> RecoverAsync(BlobItem file)
        {
            file.IsDeleted = false;
            await SetMetadataAsync(file);
            return true;
        }

        /// <summary>
        /// 物理削除
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        public override async Task DeleteBlobAsync(BlobItem file)
        {

            IEnumerable<IListBlobItem> listOfBlobs = BlobClient.ListBlobs(containerName + file.Path, true);
            foreach (IListBlobItem blobItem in listOfBlobs)
            {
                CloudBlockBlob theBlob = blobItem as CloudBlockBlob;
                //フォルダを削除する場合はそれ以下、ファイルの場合はそのファイルだけ
                if (theBlob != null && (file.Path.EndsWith("/") || theBlob.Name == file.RelativePath))
                {
                    await theBlob.DeleteIfExistsAsync(DeleteSnapshotsOption.IncludeSnapshots, null, null, null);
                }
            }
        }

        /// <summary>
        /// バージョンの一覧を取得する
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        #pragma warning disable 1998
        public override async Task<List<BlobFile>> GetHistoriesAsync(string id)
        {
            var result = new List<BlobFile>();
            var fileItem = new BlobFile(this, id);
            //retrieve all of the versions for this blob, then iterate through them
            IEnumerable<IListBlobItem> listOfBlobs = BlobClient.ListBlobs(BackupContainerName + id, true, BlobListingDetails.Snapshots | BlobListingDetails.Metadata);
            foreach (IListBlobItem blobItem in listOfBlobs)
            {
                CloudBlockBlob theBlob = blobItem as CloudBlockBlob;
                if (!theBlob.SnapshotTime.HasValue || theBlob.Name != fileItem.RelativePath) continue;
                var path = BlobUtil.UnescapeDataString(theBlob.SnapshotQualifiedUri.PathAndQuery).Substring(this.BackupContainer.Uri.PathAndQuery.Length);
                var newItem = new BlobFile(this, path);
                newItem.Length = theBlob.Properties.Length;
                newItem.LastModified = ConvertTimezone(theBlob.Properties.LastModified);
                if (theBlob.Metadata.ContainsKey(keyLastWriteTime)) newItem.LastWriteTime = ConvertTimezone(DateTimeOffset.ParseExact(theBlob.Metadata[keyLastWriteTime], "O", null));
                newItem.Snapshot = theBlob.SnapshotTime;

                result.Add(newItem);
            }
            return result;
        }
        #pragma warning restore 1998

        /// <summary>
        /// ディレクトリののファイル一覧を取得する
        /// </summary>
        /// <param name="id"></param>
        /// <param name="maxSize"></param>
        /// <returns></returns>
        public override async Task<List<BlobItem>> GetFiles(string id, int maxSize, bool flat = false, bool deleted = false)
        {
            var results = await ListBlobs(this.fileContainer, this.containerName, id, maxSize, flat, false, deleted);
            return results;
        }

        protected async Task<List<BlobItem>> ListBlobs(CloudBlobContainer cnt, string cntName, string id, int maxSize, bool flat, bool link = false, bool deleted =false)
        {
            var children = new List<BlobItem>();
            BlobContinuationToken token = null;
            var paths = new List<string>();
            while (true)
            {
                var list = await BlobClient.ListBlobsSegmentedAsync(cntName + id, flat, BlobListingDetails.Metadata, maxSize > 0 ? (int?)(maxSize + 1) : null, token, null, null);
                if (list != null)
                {
                    token = list.ContinuationToken;
                    foreach (var item in list.Results)
                    {
                        var path = BlobUtil.UnescapeDataString(item.Uri.PathAndQuery).Substring(cnt.Uri.PathAndQuery.Length);
                        paths.Add(path);
                    }
                    foreach (var item in list.Results)
                    {
                        BlobItem newItem = null;
                        var path = BlobUtil.UnescapeDataString(item.Uri.PathAndQuery).Substring(cnt.Uri.PathAndQuery.Length);
                        var directory = item as CloudBlobDirectory;
                        CloudBlockBlob blockBlob;
                        if (directory != null)
                        {
                            if (path.EndsWith("/") && paths.Contains(path.Substring(0, path.Length - 1))) continue;
                            newItem = new BlobDirectory(this, path);
                            blockBlob = cnt.GetBlockBlobReference(newItem.RelativePath);
                            if (await blockBlob.ExistsAsync()) await blockBlob.FetchAttributesAsync();
                        }
                        else
                        {
                            if (path.EndsWith("/"))
                            {
                                newItem = new BlobDirectory(this, path);
                            }
                            else
                            {
                                newItem = new BlobFile(this, path);
                            }
                            blockBlob = (CloudBlockBlob)item;

                        }
                        foreach (var prop in blockBlob.Metadata)
                        {
                            newItem.Metadata[prop.Key] = blockBlob.Metadata[prop.Key];
                        }
                        newItem.Length = blockBlob.Properties.Length;

                        //newItem.LastModified = ConvertTimezone(blockBlob.Properties.LastModified);
                        newItem.LastModified = ConvertTimezone(newItem.UploadDate ?? blockBlob.Properties.LastModified);

                        if (blockBlob.Metadata.ContainsKey(keyLastWriteTime)) newItem.LastWriteTime = ConvertTimezone(DateTimeOffset.ParseExact(blockBlob.Metadata[keyLastWriteTime], "O", null));
                        if (blockBlob.Metadata.ContainsKey(keyCreationTime)) newItem.CreationTime = ConvertTimezone(DateTimeOffset.ParseExact(blockBlob.Metadata[keyCreationTime], "O", null));

                        if (maxSize > 0 && children.Count > maxSize)
                        {
                            break;
                        }
                        else
                        {
                            if ((deleted == newItem.IsDeleted) && (string.IsNullOrEmpty(newItem.Link) || link)) children.Add(newItem);
                        }
                    }
                    if (token == null) break;
                }
                else
                {
                    break;
                }
            }
            return children;
        }


        /// <summary>
        /// メタ情報の設定
        /// </summary>
        /// <returns></returns>
        public override async Task SetMetadataAsync(BlobItem item)
        {
            var cloudBlob = GetCloudBlob(item.RelativePath);
            if (cloudBlob == null) return;
            if (!await cloudBlob.ExistsAsync())
            {
                using (var st = new MemoryStream(new byte[] { }))
                {
                    await cloudBlob.UploadFromStreamAsync(st);
                }
            }
            item.SetAccessTokenMetadataSync();

            item.Metadata[keyLastWriteTime] = item.LastWriteTime.HasValue ? item.LastWriteTime.Value.ToString("O") : null;
            item.Metadata[keyCreationTime] =  item.CreationTime.HasValue ? item.CreationTime.Value.ToString("O") : null;

            var isDirty = false;
            foreach (var prop in item.Metadata)
            {
                //if (IsNotPersistMetadataKey(prop.Key)) continue;
                if (!string.IsNullOrEmpty(item.Metadata[prop.Key]))
                {
                    if (cloudBlob.Metadata.ContainsKey(prop.Key) && cloudBlob.Metadata[prop.Key] == item.Metadata[prop.Key]) continue;
                    cloudBlob.Metadata[prop.Key] = item.Metadata[prop.Key];
                    isDirty = true;
                }
                else
                {
                    if (!cloudBlob.Metadata.ContainsKey(prop.Key)) continue;
                    cloudBlob.Metadata.Remove(prop.Key);
                    isDirty = true;
                }
            }
            if (isDirty)
            {
                //メタ更新時にLastModidiedが変更になるためコンテンツの更新日をploadDateに保存するようにしている
                if (!cloudBlob.Metadata.ContainsKey(BlobItem.keyUploadDate))
                {
                    cloudBlob.Metadata[BlobItem.keyUploadDate] = (item.LastModified ?? DateTime.UtcNow).ToString("O");
                }
                await cloudBlob.SetMetadataAsync();

            }
        }

        //protected virtual bool IsNotPersistMetadataKey(string key)
        //{
        //    return key == BlobFile.keyArchiveLastModified;
        //}

        /// <summary>
        /// メタ情報の取得
        /// </summary>
        /// <param name="fetch"></param>
        /// <returns></returns>
        public override async Task GetMetadataAsync(BlobItem item, bool fetch = true)
        {
            var cloudBlob = GetCloudBlob(item.RelativePath);
            if (cloudBlob == null) return;
            if (fetch)
            {
                var exists = await cloudBlob.ExistsAsync();
                if (exists) await cloudBlob.FetchAttributesAsync();
            }
            foreach (var prop in cloudBlob.Metadata)
            {
                item.Metadata[prop.Key] = cloudBlob.Metadata[prop.Key];
            }
            //item.LastModified = ConvertTimezone(cloudBlob.Properties.LastModified);
            item.LastModified = ConvertTimezone(item.UploadDate ?? cloudBlob.Properties.LastModified);
            item.Length = cloudBlob.Properties.Length;
            //var latestArchive = GetOptionalArchiveLastModified(cloudBlob);
            //if (latestArchive.HasValue) item.ArchiveLastModified = latestArchive;
            item.AccessTokens = null;

            if (cloudBlob.Metadata.ContainsKey(keyLastWriteTime)) item.LastWriteTime = ConvertTimezone(DateTimeOffset.ParseExact(cloudBlob.Metadata[keyLastWriteTime], "O", null));
            if (cloudBlob.Metadata.ContainsKey(keyCreationTime)) item.CreationTime = ConvertTimezone(DateTimeOffset.ParseExact(cloudBlob.Metadata[keyCreationTime], "O", null));

            if (item.LatestSnapshot.HasValue)
            {
                var snapShot = GetCloudBlob(item.RelativePath, item.LatestSnapshot.Value);
                if (snapShot == null || ! await snapShot.ExistsAsync()) item.LatestSnapshot = null;         
            }
        }

        //protected virtual DateTimeOffset? GetOptionalArchiveLastModified(CloudBlockBlob cloudBlob)
        //{
        //    return ConvertTimezone(cloudBlob.Properties.LastModified);
        //}

        protected DateTimeOffset? ConvertTimezone(DateTimeOffset? time)
        {
            return time;
        }

        internal protected virtual CloudBlockBlob GetCloudBlob(string path, DateTimeOffset? snapshot=null)
        {
            if (string.IsNullOrEmpty(path)) return null;
            var id = path  + (snapshot.HasValue ? "|" + snapshot.Value.ToString("R") : "");
            if (!CloudBlobCache.ContainsKey(id))
            {
                if (snapshot.HasValue)
                {
                    CloudBlobCache[id] = this.fileContainer.GetBlockBlobReference(path, snapshot);
                }
                else
                {
                    CloudBlobCache[id] = this.fileContainer.GetBlockBlobReference(path);
                }
            }
            return CloudBlobCache[id];

        }

        internal protected Dictionary<string, CloudBlockBlob> CloudBlobCache = new Dictionary<string, CloudBlockBlob>();

        /// <summary>
        /// アップロード
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        public override async Task UploadAsync(BlobItem item, Stream stream)
        {
            var blob = fileContainer.GetBlockBlobReference(item.RelativePath);

            if (IsEncrypted)
            {
                var blobRequestOptions = new BlobRequestOptions() { EncryptionPolicy = encryptionPolicy };
                await blob.UploadFromStreamAsync(stream, null, blobRequestOptions, null);
            }
            else
            {
                await blob.UploadFromStreamAsync(stream);
            }
        }

        /// <summary>
        /// アップロード
        /// </summary>
        /// <param name="localpath"></param>
        /// <returns></returns>
        public override async Task UploadAsync(BlobItem item, string localpath)
        {
            var blob = fileContainer.GetBlockBlobReference(item.RelativePath);

            if (IsEncrypted)
            {
                var blobRequestOptions = new BlobRequestOptions() { EncryptionPolicy = encryptionPolicy };
                await blob.UploadFromFileAsync(localpath, null, blobRequestOptions, null);
            }
            else
            {
                await blob.UploadFromFileAsync(localpath);
            }
        }

        /// <summary>
        /// バックアップ
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public override async Task<CloudBlockBlob> CreateArchiveAsync(BlobItem item)
        {
            //var baseItem = listOfBlobs.OfType<CloudBlockBlob>().Where(x => !x.SnapshotTime.HasValue).FirstOrDefault();
            //if (baseItem == null || lastSnapshot == null || baseItem.Properties.LastModified != lastSnapshot.Properties.LastModified)
            CloudBlockBlob lastSnapshot;
            if (item.IsArchibveDirty)
            {
                var blob = fileContainer.GetBlockBlobReference(item.RelativePath);
                lastSnapshot = await blob.CreateSnapshotAsync();
                lastSnapshot.Metadata[keyLastWriteTime] = (item.LastWriteTime.HasValue ? item.LastWriteTime.Value : (item.LastModified.HasValue ? item.LastModified.Value : DateTime.UtcNow)).ToString("O");
                item.LatestSnapshot = lastSnapshot.SnapshotTime;
                item.ArchiveLastModified = item.LastModified;
                await item.SetMetadataAsync();
            }
            else
            {
                lastSnapshot = fileContainer.GetBlockBlobReference(item.RelativePath, item.LatestSnapshot);
            }

            return lastSnapshot;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="item"></param>
        /// <param name="maxsize"></param>
        /// <returns></returns>
        public override async Task PurgeBackupAsync(BlobItem item, int maxsize = 20)
        {
            if (maxsize == 0) return;
            IEnumerable<IListBlobItem> listOfBlobs = BlobClient.ListBlobs(BackupContainerName + item.Path, true, BlobListingDetails.Snapshots);
            foreach (var blobItem in listOfBlobs.OfType<CloudBlockBlob>().Where(x => x.Name == item.RelativePath && x.SnapshotTime.HasValue).OrderByDescending(x => x.SnapshotTime.Value).Skip(maxsize))
            {
                await blobItem.DeleteIfExistsAsync();
            }
        }

        /// <summary>
        /// ダウンロード
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        public override async Task DownloadAsync(BlobItem item, Stream stream)
        {
            var blob = fileContainer.GetBlockBlobReference(item.RelativePath);
            if (IsEncrypted)
            {
                var blobRequestOptions = new BlobRequestOptions() { EncryptionPolicy = encryptionPolicy };
                await blob.DownloadToStreamAsync(stream, null, blobRequestOptions, null);
            }
            else
            {
                await blob.DownloadToStreamAsync(stream);
            }
        }

        /// <summary>
        /// アーカイブのダウンロード
        /// </summary>
        /// <param name="item"></param>
        /// <param name="stream"></param>
        /// <returns></returns>
        public override async Task DownloadArchiveAsync(BlobItem item, Stream stream)
        {
            var blob = fileContainer.GetBlockBlobReference(item.RelativePath, item.Snapshot);

            if (IsEncrypted)
            {
                var blobRequestOptions = new BlobRequestOptions() { EncryptionPolicy = encryptionPolicy };
                await blob.DownloadToStreamAsync(stream, null, blobRequestOptions, null);
            }
            else
            {
                await blob.DownloadToStreamAsync(stream);
            }

        }

        /// <summary>
        /// ダウンロード
        /// </summary>
        /// <param name="localpath"></param>
        /// <returns></returns>
        public override async Task DownloadAsync(BlobItem item, string localpath)
        {
            var blob = fileContainer.GetBlockBlobReference(item.RelativePath);
            if (IsEncrypted)
            {
                var blobRequestOptions = new BlobRequestOptions() { EncryptionPolicy = encryptionPolicy };
                await blob.DownloadToFileAsync(localpath, FileMode.Create, null, blobRequestOptions, null);
            }
            else
            {
                await blob.DownloadToFileAsync(localpath, FileMode.Create);
            } 
        }

        /// <summary>
        /// フォルダ作成
        /// </summary>
        /// <param name="dire"></param>
        /// <returns></returns>
        public override async Task CreateDirectoryAsync(BlobDirectory dire)
        {
            using (var st = new MemoryStream(new byte[] { }))
            {
                await UploadAsync(dire, st);
            }
        }

        /// <summary>
        /// コンテナの削除
        /// </summary>
        /// <returns></returns>
        public override bool Destory()
        {
            var tempCont = BlobClient.GetContainerReference(containerName + "-temp");
            tempCont.DeleteIfExists();
            var trashCont = BlobClient.GetContainerReference(containerName + "-trash");
            trashCont.DeleteIfExists();
            var catalogCont = BlobClient.GetContainerReference(containerName + "-catalog");
            catalogCont.DeleteIfExists();
            var fileCont = BlobClient.GetContainerReference(containerName);
            fileCont.DeleteIfExists();

            var queueClient = StorageAccount.CreateCloudQueueClient();
            var fullqueue = queueClient.GetQueueReference(containerName + "-fullqueue");
            fullqueue.DeleteIfExists();
            var crawlqueue = queueClient.GetQueueReference(containerName + "-crawlqueue");
            crawlqueue.DeleteIfExists();
            return true;
        }

        /// <summary>
        /// チェックイン
        /// </summary>
        /// <returns></returns>
        public override Task<bool> CheckInActionAsync(BlobUser user, BlobFile file)
        {
            return Task.FromResult(true);
        }

        /// <summary>
        /// チェックアウト
        /// </summary>
        /// <returns></returns>
        public override Task<bool> CheckOutActionAsync(BlobUser user, BlobFile file)
        {
            return Task.FromResult(true);
        }

        public override Task<List<BlobItem>> ListTempFile(string id)
        {
            var children = new List<BlobItem>();
            var file = new BlobItem(null, id);
            var list = fileContainer.ListBlobs(file.RelativePath, false, BlobListingDetails.Metadata);
            if (list != null)
            {
                foreach (var item in list)
                {
                    BlobItem newItem = null;
                    var path = BlobUtil.UnescapeDataString(item.Uri.PathAndQuery).Substring(fileContainer.Uri.PathAndQuery.Length);
                    newItem = new BlobFile(this, path);
                    var blockBlob = (CloudBlockBlob)item;

                    foreach (var prop in blockBlob.Metadata)
                    {
                        newItem.Metadata[prop.Key] = blockBlob.Metadata[prop.Key];
                    }
                    newItem.Length = blockBlob.Properties.Length;

                    children.Add(newItem);
                }
            }
            return Task.FromResult(children);
        }

        public override async Task LoadTempFile(BlobItem item, Stream stream)
        {
            var blob = fileContainer.GetBlockBlobReference(item.RelativePath);
            if (IsEncrypted)
            {
                var blobRequestOptions = new BlobRequestOptions() { EncryptionPolicy = encryptionPolicy };
                await blob.DownloadToStreamAsync(stream, null, blobRequestOptions, null);
            }
            else
            {
                await blob.DownloadToStreamAsync(stream);
            }
        }

        public override async Task SaveTempFile(BlobItem item, Stream stream)
        {
            var blob = fileContainer.GetBlockBlobReference(item.RelativePath);

            if (IsEncrypted)
            {
                var blobRequestOptions = new BlobRequestOptions() { EncryptionPolicy = encryptionPolicy };
                await blob.UploadFromStreamAsync(stream, null, blobRequestOptions, null);
            }
            else
            {
                await blob.UploadFromStreamAsync(stream);
            }
        }

        public override async Task DeleteTempFile(BlobItem item)
        {
            var blob = fileContainer.GetBlockBlobReference(item.RelativePath);
            await blob.DeleteIfExistsAsync(DeleteSnapshotsOption.IncludeSnapshots, null, null, null);

        }

        public override string CreateBlobSas(double expiryHour, bool canWrite)
        {
            return CreateBlobSas(fileContainer, expiryHour, canWrite);
        }

        protected virtual string CreateBlobSas(CloudBlobContainer container, double expiryHour, bool canWrite)
        {
            SharedAccessBlobPolicy sasConstraints = new SharedAccessBlobPolicy();
            sasConstraints.SharedAccessExpiryTime = DateTime.UtcNow.AddHours(expiryHour);
            if (canWrite)
            {
                sasConstraints.Permissions = SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.List;
            }
            else
            {
                sasConstraints.Permissions = SharedAccessBlobPermissions.List;
            }
            string sasContainerToken = container.GetSharedAccessSignature(sasConstraints);
            return container.Uri + sasContainerToken;
        }
    }

}
